/* Selectively compare two ar archives.
 * Usage:
 * 	ardiff [-ni] [-t name] ar1 ar2
 * Options:
 * 	-c compare member content. (This implies -s)
 * 	-n compare member name.
 * 	-i compare member mtime.
 *	-l compare archive length (member count).
 *	-s compare member size.
 *	-t specify the test name.
 *
 * By default, it compares nothing and consider the test "not ok"
 * iff it encounters errors while reading archive.
 *
 * $Id: ardiff.c 3102 2014-10-29 21:09:01Z jkoshy $
 */

#include <archive.h>
#include <archive_entry.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define COUNTER	"/tmp/bsdar-test-total"
#define PASSED	"/tmp/bsdar-test-passed"

static void	usage(void);
static void	filediff(const char *tc, const char *msg, const char *e);
static void	filesame(const char *tc);
static void	incct(const char *pathname);

int
main(int argc, char **argv)
{
	struct archive *a1;
	struct archive *a2;
	struct archive_entry *e1;
	struct archive_entry *e2;
	const char *tc;
	char *buf1;
	char *buf2;
	char checkcont;
	char checklen;
	char checkname;
	char checksize;
	char checktime;
	char a1end;
	ssize_t size1;
	ssize_t size2;
	char opt;
	int r;

	/*
	 * Parse command line options.
	 */
	checkcont = 0;
	checklen = 0;
	checkname = 0;
	checksize = 0;
	checktime = 0;
	tc = NULL;
	while ((opt = getopt(argc, argv, "cilnst:")) != -1) {
		switch(opt) {
		case 'c':
			checkcont = 1;
			break;
		case 'i':
			checktime = 1;
			break;
		case 'l':
			checklen = 1;
			break;
		case 'n':
			checkname = 1;
			break;
		case 's':
			checksize = 1;
		case 't':
			tc = optarg;
			break;
		default:
			usage();
		}
	}

	argc -= optind;
	argv += optind;
	if (argc != 2)
		usage();

	/* Open file 1 */
	a1 = archive_read_new();
	archive_read_support_format_ar(a1);
	if (archive_read_open_filename(a1, argv[0],
	    1024*10)) {
		warnx("%s", archive_error_string(a1));
		filediff(tc, "archive open failed", NULL);
	}

	/* Open file 2 */
	a2 = archive_read_new();
	archive_read_support_format_ar(a2);
	if (archive_read_open_filename(a2, argv[1],
	    1024*10)) {
		warnx("%s", archive_error_string(a2));
		filediff(tc, "archive open failed", NULL);
	}

	/* Main loop */
	a1end = 0;
	size1 = 0;
	size2 = 0;
	for (;;) {
		/*
		 * Read header from each archive, compare length.
		 */
		r = archive_read_next_header(a1, &e1);
		if (r == ARCHIVE_EOF)
			a1end = 1;
		if (r == ARCHIVE_WARN || r == ARCHIVE_RETRY ||
		    r == ARCHIVE_FATAL) {
			warnx("%s", archive_error_string(a1));
			filediff(tc, "archive data error", NULL);
		}
		r = archive_read_next_header(a2, &e2);
		if (r == ARCHIVE_EOF) {
			if (a1end > 0)
				break;
			else {
				if (checklen)
					filediff(tc, "length differ", NULL);
				break;
			}
		}
		if (r == ARCHIVE_WARN || r == ARCHIVE_RETRY ||
		    r == ARCHIVE_FATAL) {
			warnx("%s", archive_error_string(a2));
			filediff(tc, "archive data error", NULL);
		}
		if (a1end > 0) {
			if (checklen)
				filediff(tc, "length differ", NULL);
			break;
		}

		/*
		 * Check member name if required.
		 */
		if (checkname) {
			if (strcmp(archive_entry_pathname(e1),
			    archive_entry_pathname(e2)) != 0)
				filediff(tc, "member name differ",
				    archive_entry_pathname(e1));
		}

		/*
		 * Compare time if required.
		 */
		if (checktime) {
			if (archive_entry_mtime(e1) !=
			    archive_entry_mtime(e2))
				filediff(tc, "member mtime differ",
				    archive_entry_pathname(e1));
		}

		/*
		 * Compare member size if required.
		 */
		if (checksize || checkcont) {
			size1 = archive_entry_size(e1);
			size2 = archive_entry_size(e2);
			if (size1 != size2)
				filediff(tc, "member size differ",
				    archive_entry_pathname(e1));
		}

		/*
		 * Compare member content if required.
		 */
		if (checkcont) {
			if ((buf1 = malloc(size1)) == NULL)
				filediff(tc, "not enough memory", NULL);
			if ((buf2 = malloc(size2)) == NULL)
				filediff(tc, "not enough memory", NULL);
			if (archive_read_data(a1, buf1, size1) != size1)
				filediff(tc, "archive_read_data failed",
				    archive_entry_pathname(e1));
			if (archive_read_data(a2, buf2, size2) != size2)
				filediff(tc, "archive_read_data failed",
				    archive_entry_pathname(e1));
			if (memcmp(buf1, buf2, size1) != 0)
				filediff(tc, "member content differ",
				    archive_entry_pathname(e1));
			free(buf1);
			free(buf2);
		}

		/* Proceed to next header. */
	}

	/* Passed! */
	filesame(tc);
	exit(EXIT_SUCCESS);
}

static void
filediff(const char *tc, const char *msg, const char *e)
{
	if (e != NULL)
		fprintf(stdout, "%s - archive diff not ok (%s (entry: %s))\n",
		    tc, msg, e);
	else
		fprintf(stdout, "%s - archive diff not ok (%s)\n", tc, msg);

	incct(COUNTER);
	exit(EXIT_SUCCESS);
}

static void
filesame(const char *tc)
{
	fprintf(stdout, "%s - archive diff ok\n", tc);
	incct(COUNTER);
	incct(PASSED);
}

static void
incct(const char *pathname)
{
	FILE *fp;
	char buf[10];

	if ((fp = fopen(pathname, "r")) != NULL) {
		if (fgets(buf, 10, fp) != buf)
			perror("fgets");
		snprintf(buf, 10, "%d\n", atoi(buf) + 1);
		fclose(fp);
	}
	if ((fp = fopen(pathname, "w")) != NULL) {
		fputs(buf, fp);
		fclose(fp);
	}
}

static void
usage(void)
{
	fprintf(stderr, "usage: ardiff archive1 archive2\n");
	exit(EXIT_FAILURE);
}
